"""
replacement for ModelForm to make certain (or all) fields display-only
"""

import django.forms as forms
from django.db.models.fields import FieldDoesNotExist
from django.utils.encoding import force_unicode
from django.utils.html import escape, conditional_escape
from django.utils.safestring import mark_safe
from django.utils.html import linebreaks
from django.conf import settings
import datetime, time
from itertools import chain
import markdown

class DisplayModelForm(forms.ModelForm):
    """
    Subclass of ModelForm
    Can be used as normal ModelForm
    but has the following extra's:
        - making form display_only:
        - determine the widget to use when displaying as display_only
            (to add links to foreign keys and M2M fields: use the links attribute)
        and
        - adding attrs to widgets
        - modifying help_texts in widgets
        - set links on FK and M2M fields when display-only (requires the target
            to have a get_absolute_url attribute)
        - reorder fields
        - set input and output formats on datefields uses settings.DATE_INPUT_FORMATS
            and settings.DATE_OUTPUT_FORMAT

    How to use?

        Render a form display_only: this renders all the fields not with form fields
        but with normal html.

        example:
            class MyForm(DisplayModelForm):
                ...
            use form for editing:
                form = MyForm(instance=myobject)
            use form for displaying info (all fields display_only):
                form = EventForm(instance=myobject, display_only = True)
            or make only a few field display_only:
                form = EventForm(instance=myobject, display_only = True,
                    display_fields=['title','category'])


        to determine the widgets used when rendering as display_only add a
        'displaywidgets' dict to the Meta class of the ModelForm

        example:
            class MyForm(DisplayModelForm):
                class Meta:
                    displaywidgets = {
                            'location': DisplayTextURL(urlbase='/documents/')
                        }
                # PS: do this if location is a charfield, not a foreign key
                # for foreign keys, use the links attribute (see further)

        Extra's:
            - add an 'attrs' dict to the Meta class, for each field a dict of attrs
            - add a 'help_texts' dict to Meta
            - add a 'links' list of fields to Meta
            - field ordering uses the fields list (as in ModelForm)

        example:
            class MyForm(DisplayModelForm):
                class Meta:
                    model = Event
                    attrs = {
                        'title': {'size': 90},
                        }
                    help_texts = {
                        'title': 'Give me a title!'
                        }
                    links = ['category',]

    """
    def __init__(self, display_only = False, display_fields=[], *args, **kwargs):
        super(DisplayModelForm, self).__init__(*args, **kwargs)
        if display_only:
            self._make_form_display_only(display_fields)
        else:
            self._set_defaults()
            
        # add attrs to widgets
        attrs = getattr(self.Meta, 'attrs', {})
        for field in attrs:
            try:
                self.fields[field].widget.attrs.update(attrs[field])
            except:
                pass
            
        # modify help_texts
        help_texts = getattr(self.Meta, 'help_texts', {})
        for field in help_texts:
            try:
                self.fields[field].help_text = help_texts[field]
            except:
                pass
            
        # Reorder fields
        fields = list(getattr(self.Meta, 'fields', []))
        if fields:
            for f in self.fields:
                if not f in fields:
                    fields.append(f)
            self.fields.keyOrder = fields

    def _make_form_display_only(self, display_fields=[]):
        links = getattr(self.Meta, 'links', [])
        displaywidgets = getattr(self.Meta, 'displaywidgets', [])

        dfields = display_fields or self.fields
        for field in dfields:
            if not field in self.fields:
                raise FieldDoesNotExist, 'Item %s in display_fields is no field on the form' % field
            else:
                self.fields[field].help_text = u''
                # replace widgets
                if field in displaywidgets:
                    self.fields[field].widget = displaywidgets[field]
                else:
                    if isinstance(self.fields[field].widget, forms.Select):
                        if field in links:
                            newfield = DisplayLinkedModelChoiceField(self.fields[field].queryset,
                                widget = DisplaySelect,
                                label = self.fields[field].label,
                                help_text = self.fields[field].help_text,
                                )
                            self.fields[field] = newfield
                        else:
                            self.fields[field].widget = DisplaySelect(choices=self.fields[field].widget.choices)
                    elif isinstance(self.fields[field].widget, forms.SelectMultiple):
                        if field in links:
                            newfield = DisplayLinkedModelMultipleChoiceField(self.fields[field].queryset,
                                widget = DisplaySelectMultiple,
                                label = self.fields[field].label,
                                help_text = self.fields[field].help_text,
                                )
                            self.fields[field] = newfield
                        else:
                            self.fields[field].widget = DisplaySelectMultiple(choices=self.fields[field].widget.choices)
                    elif isinstance(self.fields[field].widget, forms.Textarea):
                        self.fields[field].widget = DisplayTextarea()
                    elif isinstance(self.fields[field].widget, forms.TextInput):
                        self.fields[field].widget = DisplayTextInput()
                    elif isinstance(self.fields[field].widget, forms.CheckboxInput):
                        self.fields[field].widget = DisplayCheckboxInput()
                    elif isinstance(self.fields[field].widget, forms.FileInput):
                        self.fields[field].widget = DisplayTextInput()
                    # special cases
                    if isinstance(self.fields[field], forms.DateField):
                        self.fields[field].widget = DisplayTextInputDate()
                    if isinstance(self.fields[field], forms.URLField):
                        self.fields[field].widget = DisplayTextURL()

    def _set_defaults(self):
        for field in self.fields:
            if isinstance(self.fields[field], forms.DateField):
                self.fields[field].input_formats = settings.DATE_INPUT_FORMATS
                self.fields[field].widget = forms.DateTimeInput(format=settings.DATE_OUTPUT_FORMAT)
                

# widgets

class DisplayInput(forms.Widget):
    """ basic class display data only"""
    def format_value(self, value):
        return u'%s' % conditional_escape(force_unicode(value))

    def render(self, name, value, attrs=None):
        if value is None: value = ''
        final_attrs = u''
        if attrs.get('id'):
            final_attrs = u' id="%s"' % attrs['id']
        return mark_safe(u'<div%s>%s</div>' % (final_attrs,
                self.format_value(value)))

class DisplayTextInput(DisplayInput):
    """Custom class display data only for TextInput"""
    pass

class DisplayTextarea(DisplayInput):
    """Custom class display data only for Textarea"""
    def format_value(self, value):
        if value:
            return u'%s' % linebreaks(conditional_escape(force_unicode(value)))
        return u''

class DisplayMarkdown(DisplayInput):
    """Custom class display data only for Markdown"""
    def format_value(self, value):
        if value:
            return u'%s' % markdown(force_unicode(value))
        return u''

class DisplayTextInputDate(DisplayInput):
    """Custom class display data only for Dates"""
    def format_value(self, value):
        if value:
            return u'%s' % datetime.date.strftime(value, settings.DATE_OUTPUT_FORMAT)
        return ''

class DisplayTextURL(DisplayInput):
    """Custom class display data only for URLs"""
    def __init__(self, urlbase='', *args, **kwargs):
        super(DisplayTextURL, self).__init__(*args, **kwargs)
        self.urlbase=urlbase

    def format_value(self, value):
        url = value
        if url[:4] != 'http':
            if self.urlbase:
                url = '%s%s' % (self.urlbase, url)
            else:
                url = "http://%s" % url
        return u'<a href="%s">%s</a>' % (conditional_escape(force_unicode(url)),conditional_escape(force_unicode(value)))

class DisplaySelect(forms.Select):
    def render(self, name, value, attrs=None, choices=()):
        if value is None: value = ''
        final_attrs = u''
        if attrs.get('id'):
            final_attrs = u' id="%s"' % attrs['id']
        str_value = force_unicode(value) # Normalize to string.
        output = u''
        for option_value, option_label in chain(self.choices, choices):
            option_value = force_unicode(option_value)
            if option_value == str_value:
                output = u'%s' % conditional_escape(force_unicode(option_label))
        return mark_safe(u'<div%s>%s</div>' % (final_attrs, output))

class DisplaySelectMultiple(forms.SelectMultiple):
    def render(self, name, value, attrs=None, choices=()):
        if value is None: value = []
        final_attrs = u''
        if attrs.get('id'):
            final_attrs = u' id="%s"' % attrs['id']
        output = []
        str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
        for option_value, option_label in chain(self.choices, choices):
            option_value = force_unicode(option_value)
            if option_value in str_values:
                output.append(u'%s' % conditional_escape(force_unicode(option_label)))
        if output:
            o = u'<br />'.join(output)
        else:
            o = u'---'
        return mark_safe(u'<div%s>%s</div>' % (final_attrs, o))

class DisplayCheckboxInput(DisplayInput, forms.CheckboxInput):
    def format_value(self, value):
        try:
            result = self.check_test(value)
        except: # Silently catch exceptions
            result = False
        if result:
            return u'X'
        return u'-'

# Fields

class DisplayLinkedModelChoiceField(forms.ModelChoiceField):
    """ ModelChoiceField displaying with a link
    """
    def label_from_instance(self, obj):
        value = super(DisplayLinkedModelChoiceField, self).label_from_instance(obj)
        if hasattr(obj, 'get_absolute_url'):
            url = obj.get_absolute_url()
            return mark_safe(u'<a href="%s">%s</a>' % (url, value))
        return value

class DisplayLinkedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
    """ ModelMultipleChoiceField displaying with links
    """
    def label_from_instance(self, obj):
        value = super(DisplayLinkedModelMultipleChoiceField, self).label_from_instance(obj)
        if hasattr(obj, 'get_absolute_url'):
            url = obj.get_absolute_url()
            return mark_safe(u'<a href="%s">%s</a>' % (url, value))
        return value
